<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.7.7/joint.css"/>
    <link rel="stylesheet" type="text/css" href="joint.ui.navigator.css">
    <link rel="stylesheet" type="text/css" href="joint.ui.paperScroller.css">
</head>
<body>
<!-- content -->

<div id="navigator"
     style="position: fixed; left: 100%; top: 100%; margin-left: -300px; margin-top: -200px; z-index: 10;"></div>
<div id="paper-container"></div>

<!-- dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.7.7/joint.js"></script>
<script src="joint.layout.treeLayout.js"></script>
<script src="joint.ui.paperScroller.js"></script>
<script src="joint.ui.navigator.js"></script>
<script src="./graphlib.min.js"></script>
<script src="./dagre.min.js"></script>
<!-- code -->
<script type="module">
  import {billGraph, reversedBillGraph, billTypeGraph, groupBy, data} from "./graph1.mjs";
  import {registerCard} from './card.mjs'

  const namespace = joint.shapes;
  const graph = new joint.dia.Graph({}, {cellNamespace: namespace});
  const paper = new joint.dia.Paper({
    model: graph,
    width: 3600,
    height: 1800,
    cellViewNamespace: namespace
  });
  const paperScroller = new joint.ui.PaperScroller({
    paper: paper
  });
  $('#paper-container').append(paperScroller.render().el);
  const nav = new joint.ui.Navigator({
    paperScroller: paperScroller,
    width: 300,
    height: 200,
    padding: 10,
    zoomOptions: {max: 2, min: 0.2}
  });
  nav.$el.appendTo('#navigator');
  nav.render();

  graph.on('remove', function (cell) {
    const cellType = cell.attributes.type
    const data = cell.attributes.attrs.data
    if (cellType !== 'standard.Link') {
      // 关闭单据类型时，要把下属的单据移除，以及打开的上下游单据移除
      const id = data.id
      openDownBill = openDownBill.filter((bill) => bill !== id)
      // TODO 移除上游单据
      // 移除下游单据
      billGraph.edges[id].forEach((targetId) => {
        if (vertices[targetId]) {
          vertices[targetId].remove()
          vertices[targetId] = null
        }
      })
    }
  })

  graph.on('add', function (cell) {
    const cellType = cell.attributes.type
    const data = cell.attributes.attrs.data
    if (cellType !== 'standard.Link' && data?.type === 'bill') {
      const id = data.id
      // TODO 关闭其他 bill type

    }
  })

  paper.on('cell:pointerclick', function (cellView) {
    console.log(cellView, cellView.model.attributes.attrs.data)
    const isElement = cellView.model.isElement();
    if (!isElement) {
      return
    }
    const data = cellView.model.attributes.attrs.data
    if (data.type === 'billType') {
      const billType = data.name
      openBillType[billType] = !openBillType[billType];
      if (openBillType[billType]) {
        drawBillGraph(billType, vertices[billType])
      } else {
        // 反向操作，把相关元素移除graph
        billTypeGraph.edges[billType].forEach((bill_vertex) => {
          if (vertices[bill_vertex]) {
            vertices[bill_vertex].remove()
            vertices[bill_vertex] = null
          }
        })
      }
    } else if (data.type === 'bill') {
      const exist = openDownBill.some(i => i === data.id)
      if (exist) {
        // 当前单据已打开，从openDownBill数组中移除，并将边移除
        openDownBill = openDownBill.filter(i => i !== data.id)
        billGraph.edges[data.id].forEach((target) => {
          if (vertices[target]) {
            vertices[target].remove()
            vertices[target] = null
          }
        })
      } else {
        openDownBill.push(data.id)
        if (vertices[data.id]) {
          openDown(data.id, vertices[data.id])
        } else {
          console.error(data.id)
        }
      }
    }
  });

  let openBillType = {
    // 'ApPayment': true,
    // 'Payment': true,
  }
  let openDownBill = [
    // '0EXQ6461URL00EE',
  ]
  let openUpBill = []

  const position = {
    main: {
      x: 200,
      y: 100,
      width: 240,
      height: 83,
    },
    padding: {
      x: 40,
      y: 30,
    }
  }

  registerCard()
  let rect = makeMainRect();

  function makeMainRect() {
    const rect = new joint.shapes.standard.Rectangle({z: 2});
    rect.position(position.main.x, position.main.y);
    rect.resize(position.main.width, position.main.height);
    rect.attr({
      body: {
        // fill: 'blue'
      },
      label: {
        // text: '0',
        // fill: 'white'
      }
    });
    rect.attr('label/text', data.curObject.id);
    rect.addTo(graph);
    return rect;
  }

  // graph
  const vertices = {}

  function drawBillTypeGraph() {
    vertices[data.curObject.id] = rect
    billTypeGraph.edges[data.curObject.id].forEach((target, i) => {
      const rect2 = rect.clone();
      const basePosition = rect.position();
      rect2.attr('label/text', target + ':' + billTypeGraph.edges[target].length);
      rect2.attr('data', {name: target, type: 'billType'})
      rect2.addTo(graph);
      rect2.position(basePosition.x + (position.main.width + position.padding.x) * (i + 1), basePosition.y);
      vertices[target] = rect2
      // 初始展开全部
      // drawBillGraph(target, vertices[target])
    })
    billTypeGraph.edges[data.curObject.id].forEach((target) => {
      const link = makeLine();
      link.source(vertices[data.curObject.id])
      link.target(vertices[target])
      link.addTo(graph);
    })
  }

  /**
   * 单据类型打开后，渲染下属的单据
   * @param billType
   * @param billTypeEle
   */
  function drawBillGraph(billType, billTypeEle) {
    const billTypeEdges = billTypeGraph.edges[billType]
    billTypeEdges.forEach((bill_vertex, i) => {
      const rect2 = makeRect(bill_vertex)
      // Y轴方向
      const basePosition = billTypeEle.position()
      rect2.position(basePosition.x, basePosition.y + (position.main.height + position.padding.y) * (i + 1));
      vertices[bill_vertex] = rect2
    })
    // const lastBill = billTypeEdges[billTypeEdges.length - 1]
    // makePageButton(lastBill)
  }

  function openDown(bill, billEle) {
    if (!isIncludeSubGraph() || openDownBill.length === 1) {
      // 先关闭其他单据类型的单据
      closeOtherBillType(bill)
    }

    // 下级单据按单据类型分组，获取类型卡片的位置作为基准位置，y轴展开，
    const downBills = billGraph.edges[bill].map(id => data.objects.find(i => i.id === id))
    const billTypeGroup = groupBy(downBills, 'objectType')
    Object.entries(billTypeGroup).forEach(([billType, bills]) => {
      const billCard = vertices[billType]
      if (billCard) {
        const basePosition = billCard.position()
        bills.forEach((target, i) => {
          const rect2 = makeRect(target.id)
          rect2.position(basePosition.x, basePosition.y + (position.main.height + position.padding.y) * (i + 1))
          const link = makeLine();
          link.source(billEle)
          link.target(rect2)
          link.addTo(graph);
        })
      }
    })
  }

  function openUp(bill, billEle) {
    reversedBillGraph.edges[bill].forEach((target) => {
      let rect2;
      if (vertices[target]) {
        rect2 = vertices[target]
      } else {
        rect2 = rect.clone();
        const down_count = billGraph.edges[target].length
        const up_count = reversedBillGraph.edges[target].length
        rect2.attr('label/text', up_count + ':' + target + ':' + down_count);
        rect2.addTo(graph);
      }
      const link = makeLine();
      link.source(billEle)
      link.target(rect2)
      link.addTo(graph);
    })
  }

  function drawAll() {
    drawBillTypeGraph()
  }

  // drawAll()
  drawBillTypeGraph()
  Object.entries(openBillType).forEach(([billType, value]) => {
    if (value) {
      drawBillGraph(billType, vertices[billType])
    }
  })
  openDownBill.forEach(bill => {
    if (!vertices[bill]) {
      console.log(bill)
    }
    openDown(bill, vertices[bill])
  })
  // openUpBill.forEach(bill => {
  //   openUp(bill, vertices[bill])
  // })

  function makeLine() {
    return new joint.shapes.standard.Link({z: 1})
  }

  function makeRect(bill_id) {
    let rect2
    if (vertices[bill_id]) {
      rect2 = vertices[bill_id]
    } else {
      rect2 = rect.clone();
      const down_count = billGraph.edges[bill_id].length
      const up_count = reversedBillGraph.edges[bill_id].length
      const billInfo = data.objects.find(i => i.id === bill_id)
      rect2.attr('label/text', up_count + ':' + billInfo?.id + ':' + down_count);
      rect2.attr('card_type/text', billInfo?.cardFieldInfo?.objectType);
      rect2.attr('data', {id: bill_id, type: 'bill'})
      vertices[bill_id] = rect2
      rect2.addTo(graph);
    }
    return rect2
  }

  function makePageButton(bill_id) {
    const rect = new joint.shapes.standard.Rectangle({z: 2});
    rect.position(position.main.x, position.main.y);
    rect.resize(position.main.width, position.main.height);
    rect.attr({
      body: {
        fill: 'blue'
      },
      label: {
        text: '0',
        fill: 'white'
      }
    });
    rect.attr('label/text', data.curObject.id);
    rect.addTo(graph);
    vertices[bill_id] = rect
  }

  function initTypeOpen() {
    Object.entries(openBillType).forEach(([billType, value]) => {
      if (value) {
        drawBillGraph(billType, vertices[billType])
      } else {
        // 反向操作，把相关元素移除graph
        billTypeGraph.edges[billType].forEach((bill_vertex) => {
          if (vertices[bill_vertex]) {
            vertices[bill_vertex].remove()
            vertices[bill_vertex] = null
          }
        })
      }
    })
  }

  /**
   * 除了参数单据的单据类型，其他单据类型都要关闭
   * @param {string} bill
   */
  function closeOtherBillType(bill) {
    const objectType = data.objects.find(i => i.id === bill).objectType
    Object.keys(openBillType).forEach((key) => {
      if (key !== objectType) {
        openBillType[key] = false
        billTypeGraph.edges[key].forEach((bill_vertex) => {
          if (vertices[bill_vertex]) {
            vertices[bill_vertex].remove()
            vertices[bill_vertex] = null
          }
        })
      }
    })
  }

  /**
   * 判断所有结点是否包含在一个子图内，以第一个元素为子图的开始。
   */
  function isIncludeSubGraph() {
    if (openDownBill.length < 2) {
      return true
    }
    const first = openDownBill[0]
    for (let i = 1; i < openDownBill.length; i++) {
      if (!billGraph.dfs(first, openDownBill[i])) {
        return false
      }
    }
    return true
  }

</script>
</body>
</html>
